package web.model;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.security.SecureRandom;

/**
 * A universally unique identifier (UUID).
 * A UUID is a 128-bit value.
 * The most significant long can be decomposed into the following
 * unsigned fields:
 *
 * 0xFFFFFFFF00000000 time_low
 * 0x00000000FFFF0000 time_mid
 * 0x000000000000F000 version
 * 0x0000000000000FFF time_hi
 *
 * The least significant long can be decomposed into the following
 * unsigned fields:
 *
 * 0xC000000000000000 variant
 * 0x3FFF000000000000 clock_seq
 * 0x0000FFFFFFFFFFFF node
 *
 * The variant field must be 0x2. The version field must be either 0x1 or 0x4.
 * If the version field is 0x4, then the most significant bit of the node
 * field must be set to 1, and the remaining fields are set to values
 * produced by a cryptographically strong pseudo-random number generator.
 * If the version field is 0x1, then the node field is set to an IEEE 802
 * address, the clock_seq field is set to a 14-bit random number, and the
 * time_low, time_mid, and time_hi fields are set to the least, middle and
 * most significant bits (respectively) of a 60-bit timestamp measured in
 * 100-nanosecond units since midnight, October 15, 1582 UTC.
 */
public final class UUID implements Serializable, Comparable<UUID> {

	private static final long serialVersionUID = 1L;

	/**
	 * The most significant 64 bits.
	 *
	 * @serial
	 */
	protected final long mostSig;

	/**
	 * The least significant 64 bits.
	 *
	 * @serial
	 */
	protected final long leastSig;

	protected static SecureRandom secRand = new SecureRandom();

	/** <B>KPT - 24 Feb 2000 -- RAW Liquidation</B>
	 * <br>
	 * Construct a <I>real</I> <CODE>UUID</CODE> from the <CODE>String</CODE> returned from the database.
	 * @param aNewFormatUUIDString The <CODE>String</CODE> representation of a <CODE>UUID</CODE> value.
	 */
	public UUID(String aNewFormatUUIDString) {
		long theMostSig = 0L;
		long theLeastSig = 0L;
		char[] charString = aNewFormatUUIDString.toCharArray();

		try {
			long leftMost = parseLong(charString, 0, 8);
			long middleMost = parseLong(charString, 8, 12);
			long rightMost = parseLong(charString, 12, 16);
			long leftLeast = parseLong(charString, 16, 20);
			long rightLeast = parseLong(charString, 20, 32);

			theMostSig = (leftMost << 32) | (middleMost << 16) | (rightMost);
			theLeastSig = (leftLeast << 48) | rightLeast;
		} catch (NumberFormatException e) {
			throw new Error("UUID.toUUID(): " + aNewFormatUUIDString + " " + e);
		}
		mostSig = theMostSig;
		leastSig = theLeastSig;
	}

	private static long parseLong(char[] chars, int start, int end) throws NumberFormatException {
		long ret = 0L;
		if (chars.length < (end - start + 1)) {
			throw new NumberFormatException("Input String is too small");
		}
		for (int i = start; i < end; i++) {
			ret *= 16L;
			int c = 0;
			switch (chars[i]) {
				case '0' :
					c = 0;
					break;
				case '1' :
					c = 1;
					break;
				case '2' :
					c = 2;
					break;
				case '3' :
					c = 3;
					break;
				case '4' :
					c = 4;
					break;
				case '5' :
					c = 5;
					break;
				case '6' :
					c = 6;
					break;
				case '7' :
					c = 7;
					break;
				case '8' :
					c = 8;
					break;
				case '9' :
					c = 9;
					break;
				case 'a' :
				case 'A' :
					c = 10;
					break;
				case 'b' :
				case 'B' :
					c = 11;
					break;
				case 'c' :
				case 'C' :
					c = 12;
					break;
				case 'd' :
				case 'D' :
					c = 13;
					break;
				case 'e' :
				case 'E' :
					c = 14;
					break;
				case 'f' :
				case 'F' :
					c = 15;
					break;
				default :
					throw new NumberFormatException();
			}
			ret += c;
		}
		return ret;
	}

	/**
	 * No-arg constructor.
	 */
	public UUID() {
		/** 128-bit buffer for use with secRand */
		byte[] secRandBuf16 = new byte[16];

		secRand.nextBytes(secRandBuf16);
		secRandBuf16[6] &= 0x0f;
		secRandBuf16[6] |= 0x40; /* version 4 */
		secRandBuf16[8] &= 0x3f;
		secRandBuf16[8] |= 0x80; /* IETF variant */
		secRandBuf16[10] |= 0x80; /* multicast bit */
		long mostSig = 0L;
		for (int i = 0; i < 8; i++) {
			mostSig = (mostSig << 8) | (secRandBuf16[i] & 0xff);
		}
		long leastSig = 0L;
		for (int i = 8; i < 16; i++) {
			leastSig = (leastSig << 8) | (secRandBuf16[i] & 0xff);
		}
		this.mostSig = mostSig;
		this.leastSig = leastSig;
	}

	/**
	 * Simple constructor.
	 *
	 * @param mostSig the most significant 64 bits
	 * @param leastSig the lease significant 64 bits
	 */
	public UUID(long mostSig, long leastSig) {
		this.mostSig = mostSig;
		this.leastSig = leastSig;
	}

	/**
	 * Reads in 16 bytes in standard network byte order.
	 *
	 * @param in the input stream to read 16 bytes from
	 */
	public UUID(DataInput in) throws IOException {
		mostSig = in.readLong();
		leastSig = in.readLong();
	}

	public UUID(byte[] bytes) {
		try {
			ByteArrayInputStream is = new ByteArrayInputStream(pad16(bytes));
			DataInputStream dis = new DataInputStream(is);
			mostSig = dis.readLong();
			leastSig = dis.readLong();
			is.close();
			dis.close();
		} catch (IOException ioe) {
			throw new Error("UUID(): this should never happen: " + ioe);
		}
	}

	protected byte[] pad16(byte[] bytes) {
		if (bytes.length == 16) {
			return bytes;
		}
		byte[] padded = new byte[16];

		for (int i = 0; i < bytes.length; i++) {
			padded[i] = bytes[i];
		}
		for (int i = bytes.length; i < 16; i++) {
			padded[i] = 0;
		}
		return padded;
	}

	/** Returns the most significant 64 bits of the UUID. */
	public long getMostSignificantBits() {
		return mostSig;
	}

	/** Returns the least significant 64 bits of the UUID. */
	public long getLeastSignificantBits() {
		return leastSig;
	}

	/**
	 * Writes out 16 bytes in standard network byte order.
	 *
	 * @param out the output stream to write 16 bytes to
	 */
	public void writeBytes(DataOutput out) throws IOException {
		out.writeLong(mostSig);
		out.writeLong(leastSig);
	}

	public byte[] getBytes() {
		try {
			ByteArrayOutputStream bs = new ByteArrayOutputStream();
			DataOutputStream ds = new DataOutputStream(bs);
			writeBytes(ds);
			return bs.toByteArray();
		} catch (IOException ioe) {
			return null;
		}
	}

	@Override
	public int hashCode() {
		return (int) ((mostSig >> 32) ^ mostSig ^ (leastSig >> 32) ^ leastSig);
	}

	@Override
	public int compareTo(UUID sid) {
		int result = Long.valueOf(mostSig).compareTo(sid.mostSig);
		if (result == 0) {
			result = Long.valueOf(leastSig).compareTo(sid.leastSig);
		}
		return result;
	}

	/**
	 * UUIDs are equal if they represent the same 128-bit value.
	 */
	@Override
	public boolean equals(Object sid) {
		if (!(sid instanceof UUID)) {
			return false;
		}
		return equals((UUID) sid);
	}

	public boolean equals(UUID sid) {
		return mostSig == sid.mostSig && leastSig == sid.leastSig;
	}

	/**
	 * Returns a 32-character string in uppercase hexadecimal with the same
	 * number of digits as in the field. The order of fields is: time_low,
	 * time_mid, version and time_hi treated as a single field, variant and
	 * clock_seq treated as a single field, and node.
	 */
	@Override
	public String toString() { /* kpt removed whitespace as per HEXTORAW */
		String r =
			(digits(mostSig >> 32, 8)
			+ digits(mostSig >> 16, 4)
			+ digits(mostSig, 4)
			+ digits(leastSig >> 48, 4)
			+ digits(leastSig, 12));
		return r.toUpperCase(); /* Convert to upper case to preserve string comparison semantics */
	}

	/**
	 * Returns a 36-character string of six fields separated by hyphens,
	 * with each field represented in lowercase hexadecimal with the same
	 * number of digits as in the field. The order of fields is: time_low,
	 * time_mid, version and time_hi treated as a single field, variant and
	 * clock_seq treated as a single field, and node.
	 */
	public String toStringWithDashes() {
		String r =
			(digits(mostSig >> 32, 8)
			+ "-"
			+ digits(mostSig >> 16, 4)
			+ "-"
			+ digits(mostSig, 4)
			+ "-"
			+ digits(leastSig >> 48, 4)
			+ "-"
			+ digits(leastSig, 12));
		return r.toLowerCase();
	}

	/** Returns val represented by the specified number of hex digits. */
	private static String digits(long val, int digits) {
		long hi = 1L << (digits * 4);
		return Long.toHexString(hi | (val & (hi - 1L))).substring(1);
	}

	protected String pad(String paddedString) {
		if (paddedString.length() < 16) {
			int size = paddedString.length();
			for (int i = size; i < 16; i++) {
				paddedString += " ";
			}
		}
		return paddedString;
	}

	/**
	 * Extract the time for the UUID and return as a Date
	 * @return Date
	 */
	public java.util.Date getBornDate() {
		// we need to convert from 100-naonsecond units (as used in UUIDs)
		// to millisecond units as used in UTC based time
		final long MILLI_CONVERSION_FACTOR = 10000L;
		// Since System.currentTimeMillis() returns time epoc time
		// (from 1-Jan-1970), and UUIDs use time from the beginning of
		// Gregorian calendar (15-Oct-1582) we have a offset for correction
		final long GREGORIAN_CALENDAR_START_TO_UTC_START_OFFSET = 122192928000000000L;

		byte[] temp_uuid = getBytes();

		// first we'll collect the UUID time stamp which is
		// the number of 100-nanosecond intervals since
		// 00:00:00.00 15 October 1582
		long uuid_time = 0L;
		uuid_time |= ((temp_uuid[3] & 0xF0L) <<  0);
		uuid_time |= ((temp_uuid[2] & 0xFFL) <<  8);
		uuid_time |= ((temp_uuid[1] & 0xFFL) << 16);
		uuid_time |= ((temp_uuid[0] & 0xFFL) << 24);
		uuid_time |= ((temp_uuid[5] & 0xFFL) << 32);
		uuid_time |= ((temp_uuid[4] & 0xFFL) << 40);
		uuid_time |= ((temp_uuid[7] & 0xFFL) << 48);
		uuid_time |= ((temp_uuid[6] & 0x0FL) << 56);

		// first we'll remove the gregorian offset
		uuid_time -= GREGORIAN_CALENDAR_START_TO_UTC_START_OFFSET;

		// and convert to milliseconds as the system clock is in millis
		uuid_time /= MILLI_CONVERSION_FACTOR;

		return(new java.util.Date(uuid_time));
	}
}